#include "Geometry.h"

namespace GeorgeLib
{

	void DrawAxes(float size)
	{
		glPushMatrix();	glPushAttrib(GL_ALL_ATTRIB_BITS);
		for (int i = 0; i < 3; ++i)
		{
			for (int j = 0; j < 2; ++j)
			{
				glLineWidth(((j) ? 10.0f : .5f));
				glBegin(GL_LINES);
				switch (i)
				{
				case 0: glColor3f(1, 0, 0); glVertex3f(size*((j) ? 1 : -1), 0, 0); break;
				case 1: glColor3f(0, 1, 0); glVertex3f(0, size*((j) ? 1 : -1), 0); break;
				case 2: glColor3f(0, 0, 1); glVertex3f(0, 0, size*((j) ? 1 : -1)); break;
				}
				glVertex3f(0, 0, 0);
				glEnd();
			}
		}
		glPopMatrix();	glPopAttrib();
	}

	void DrawCube(float size)
	{
		DrawCuboid(vec3(size), vec3(-size));
	}

	void DrawCuboid(vec3 RightTopFront, vec3 LeftBottomBack)
	{
		glBegin(GL_QUADS);

		//RIGHT FACE
		glNormal3f(1, 0, 0);	glVertex3f(RightTopFront.x, RightTopFront.y, RightTopFront.z);
		glNormal3f(1, 0, 0);	glVertex3f(RightTopFront.x, LeftBottomBack.y, RightTopFront.z);
		glNormal3f(1, 0, 0);	glVertex3f(RightTopFront.x, LeftBottomBack.y, LeftBottomBack.z);
		glNormal3f(1, 0, 0);	glVertex3f(RightTopFront.x, RightTopFront.y, LeftBottomBack.z);

		//LEFT FACE
		glNormal3f(-1, 0, 0);	glVertex3f(LeftBottomBack.x, RightTopFront.y, RightTopFront.z);
		glNormal3f(-1, 0, 0);	glVertex3f(LeftBottomBack.x, RightTopFront.y, LeftBottomBack.z);
		glNormal3f(-1, 0, 0);	glVertex3f(LeftBottomBack.x, LeftBottomBack.y, LeftBottomBack.z);
		glNormal3f(-1, 0, 0);	glVertex3f(LeftBottomBack.x, LeftBottomBack.y, RightTopFront.z);

		//TOP FACE
		glNormal3f(0, 1, 0);	glVertex3f(RightTopFront.x, RightTopFront.y, RightTopFront.z);
		glNormal3f(0, 1, 0);	glVertex3f(RightTopFront.x, RightTopFront.y, LeftBottomBack.z);
		glNormal3f(0, 1, 0);	glVertex3f(LeftBottomBack.x, RightTopFront.y, LeftBottomBack.z);
		glNormal3f(0, 1, 0);	glVertex3f(LeftBottomBack.x, RightTopFront.y, RightTopFront.z);

		//BOTTOM FACE
		glNormal3f(0, -1, 0);	glVertex3f(RightTopFront.x, LeftBottomBack.y, RightTopFront.z);
		glNormal3f(0, -1, 0);	glVertex3f(LeftBottomBack.x, LeftBottomBack.y, RightTopFront.z);
		glNormal3f(0, -1, 0);	glVertex3f(LeftBottomBack.x, LeftBottomBack.y, LeftBottomBack.z);
		glNormal3f(0, -1, 0);	glVertex3f(RightTopFront.x, LeftBottomBack.y, LeftBottomBack.z);

		//FRONT FACE
		glNormal3f(0, 0, 1);	glVertex3f(RightTopFront.x, RightTopFront.y, RightTopFront.z);
		glNormal3f(0, 0, 1);	glVertex3f(LeftBottomBack.x, RightTopFront.y, RightTopFront.z);
		glNormal3f(0, 0, 1);	glVertex3f(LeftBottomBack.x, LeftBottomBack.y, RightTopFront.z);
		glNormal3f(0, 0, 1);	glVertex3f(RightTopFront.x, LeftBottomBack.y, RightTopFront.z);

		//BACK FACE
		glNormal3f(0, 0, -1);	glVertex3f(RightTopFront.x, RightTopFront.y, LeftBottomBack.z);
		glNormal3f(0, 0, -1);	glVertex3f(RightTopFront.x, LeftBottomBack.y, LeftBottomBack.z);
		glNormal3f(0, 0, -1);	glVertex3f(LeftBottomBack.x, LeftBottomBack.y, LeftBottomBack.z);
		glNormal3f(0, 0, -1);	glVertex3f(LeftBottomBack.x, RightTopFront.y, LeftBottomBack.z);

		glEnd();
	}

#pragma region spherefuncs

	float __x(float phi, float theta, float radius)
	{
		phi *= DEGTORAD;
		theta *= DEGTORAD;
		return radius*cos(phi)*cos(theta);
	}

	float __y(float phi, float theta, float radius)
	{
		phi *= DEGTORAD;
		theta *= DEGTORAD;
		return radius*sin(phi);
	}

	float __z(float phi, float theta, float radius)
	{
		phi *= DEGTORAD;
		theta *= DEGTORAD;
		return radius*cos(phi)*sin(theta);
	}

#pragma endregion

	vec3 GetQuadNormal(vec3 vertex[], short arrsize)
	{
		vec3 temp;
		for (int i = 0; i < arrsize; ++i)
		{
			temp += vertex[i] * vertex[(i + 1) % arrsize];	// calculate normal vector using cross product
		}

		return temp.normal();	// normalize the vector
	}

	void DrawSphere(float radius, float slices, float stacks)
	{
		float stackdiff = 180 / stacks;
		float slicediff = 360 / slices;
		vec3 normal, vertex[4];

		glBegin(GL_QUADS);

		for (float phi = -90; phi < 90; phi += stackdiff)
		{
			for (float theta = 0; theta < 360; theta += slicediff)
			{
				vertex[0](__x(phi, theta, radius),						// get vertex #1
					__y(phi, theta, radius),
					__z(phi, theta, radius));
				vertex[1](__x(phi + stackdiff, theta, radius),				// get vertex #2
					__y(phi + stackdiff, theta, radius),
					__z(phi + stackdiff, theta, radius));
				vertex[2](__x(phi + stackdiff, theta + slicediff, radius),	// get vertex #3
					__y(phi + stackdiff, theta + slicediff, radius),
					__z(phi + stackdiff, theta + slicediff, radius));
				vertex[3](__x(phi, theta + slicediff, radius),				// get vertex #4
					__y(phi, theta + slicediff, radius),
					__z(phi, theta + slicediff, radius));

				normal = GetQuadNormal(vertex, 4);

				for (int i = 0; i < 4; ++i)
				{
					glNormal3f(normal.x, normal.y, normal.z);
					glVertex3f(vertex[i].x, vertex[i].y, vertex[i].z);
				}
			}
		}

		glEnd();
	}

	void DrawHemisphere(float radius, float slices, float stacks)
	{
		float stackdiff = 90 / stacks;
		float slicediff = 360 / slices;
		vec3 normal, vertex[4];

		glBegin(GL_QUADS);

		for (float phi = 0; phi < 90; phi += stackdiff)
		{
			for (float theta = 0; theta < 360; theta += slicediff)
			{
				vertex[0](__x(phi, theta, radius),						// get vertex #1
					__y(phi, theta, radius),
					__z(phi, theta, radius));
				vertex[1](__x(phi + stackdiff, theta, radius),				// get vertex #2
					__y(phi + stackdiff, theta, radius),
					__z(phi + stackdiff, theta, radius));
				vertex[2](__x(phi + stackdiff, theta + slicediff, radius),	// get vertex #3
					__y(phi + stackdiff, theta + slicediff, radius),
					__z(phi + stackdiff, theta + slicediff, radius));
				vertex[3](__x(phi, theta + slicediff, radius),				// get vertex #4
					__y(phi, theta + slicediff, radius),
					__z(phi, theta + slicediff, radius));

				normal = GetQuadNormal(vertex, 4);

				for (int i = 0; i < 4; ++i)
				{
					glNormal3f(normal.x, normal.y, normal.z);
					glVertex3f(vertex[i].x, vertex[i].y, vertex[i].z);
				}
			}
		}

		glEnd();
	}

	void DrawCylinder(float outer_radius, float inner_radius, float height, float slices)
	{
		float slicediff = 360 / slices;
		vec3 normal, vertex_outside[4], vertex_inside[4], vertex_cap[4];

		for (float theta = 0; theta < 360; theta += slicediff)
		{
			glBegin(GL_QUADS);	// outside

			vertex_outside[0](__x(height, theta, outer_radius),				// get vertex #1
				height,
				__z(height, theta, outer_radius));
			vertex_outside[1](__x(height, theta + slicediff, outer_radius),		// get vertex #2
				height,
				__z(height, theta + slicediff, outer_radius));
			vertex_outside[2](__x(-height, theta + slicediff, outer_radius),	// get vertex #3
				-height,
				__z(-height, theta + slicediff, outer_radius));
			vertex_outside[3](__x(-height, theta, outer_radius), 				// get vertex #4
				-height,
				__z(-height, theta, outer_radius));

			normal = GetQuadNormal(vertex_outside, 4);

			for (int i = 0; i < 4; ++i)
			{
				glNormal3f(normal.x, normal.y, normal.z);
				glVertex3f(vertex_outside[i].x, vertex_outside[i].y, vertex_outside[i].z);
			}

			if (inner_radius > 0)
			{
				// inside
				vertex_inside[0](__x(height, theta, inner_radius),				// get vertex #1
					height,
					__z(height, theta, inner_radius));
				vertex_inside[1](__x(-height, theta, inner_radius), 				// get vertex #2
					-height,
					__z(-height, theta, inner_radius));
				vertex_inside[2](__x(-height, theta + slicediff, inner_radius),	// get vertex #3
					-height,
					__z(-height, theta + slicediff, inner_radius));
				vertex_inside[3](__x(height, theta + slicediff, inner_radius),		// get vertex #4
					height,
					__z(height, theta + slicediff, inner_radius));

				normal = GetQuadNormal(vertex_inside, 4);

				for (int i = 0; i < 4; ++i)
				{
					glNormal3f(normal.x, normal.y, normal.z);
					glVertex3f(vertex_inside[i].x, vertex_inside[i].y, vertex_inside[i].z);
				}
			}
			// top and bottom

			for (int i = 0; i < 2; ++i)
			{
				vertex_cap[0] = vertex_outside[((i) ? 3 : 1)];
				vertex_cap[1] = vertex_outside[((i) ? 2 : 0)];
				vertex_cap[2](__x(height*((i) ? -1 : 1), theta, inner_radius),
					height*((i) ? -1 : 1),
					__z(height*((i) ? -1 : 1), theta, inner_radius));
				vertex_cap[3](__x(height*((i) ? -1 : 1), theta + slicediff*((i) ? -1 : 1), inner_radius),
					height*((i) ? -1 : 1),
					__z(height*((i) ? -1 : 1), theta + slicediff*((i) ? -1 : 1), inner_radius));

				normal = GetQuadNormal(vertex_cap, 4);

				for (int j = 0; j < 4; ++j)
				{
					glNormal3f(normal.z, normal.y, normal.z);
					glVertex3f(vertex_cap[j].x, vertex_cap[j].y, vertex_cap[j].z);
				}
			}

			glEnd();
		}
	}

	void DrawSquare(float size)
	{
		DrawRectangle(vec2(size), vec2(size));
	}

	void DrawRectangle(vec2 RightTop, vec2 LeftBottom)
	{
		glBegin(GL_QUADS);

		glVertex2f(RightTop.x, RightTop.y);
		glVertex2f(RightTop.x, LeftBottom.y);
		glVertex2f(LeftBottom.x, LeftBottom.y);
		glVertex2f(LeftBottom.x, RightTop.y);

		glEnd();
	}

	void DrawCircle(float rad, float slices)
	{
		float slicediff = 360 / slices;
		vec2 vertex[3];

		glBegin(GL_TRIANGLES);

		for (float phi = 0; phi < 360; phi += slicediff)
		{
			// cos for x-axis, sin for y-axis
			vertex[0](rad * cos(phi*DEGTORAD), rad * sin(phi*DEGTORAD));
			vertex[1](0, 0);
			vertex[2](rad * cos((phi + slicediff)*DEGTORAD), rad * sin((phi + slicediff)*DEGTORAD));
		}

		glEnd();
	}

} // namespace GeorgeLib